/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *            Copyright (C) 2018 Institute of Computing Technology, CAS
 *               Author : Han Shukai (email : hanshukai@ict.ac.cn)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *         The kernel's entry, where most of the initialization work is done.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * */

#include <common.h>
#include <pgtable.h>
#include <user_programs.h>
#include <os/irq.h>
#include <os/mm.h>
#include <os/sched.h>
#include <screen.h>
#include <sbi.h>
#include <stdio.h>
#include <os/time.h>
#include <os/syscall.h>
// #include <os/futex.h>
#include <os/binsem.h>
#include <os/smp.h>
#include <os/ioremap.h>
#include <os/elf.h>
#include <os/string.h>

#include <csr.h>

#include <plic.h>
#include <emacps/xemacps_example.h>
#include <net.h>
#include <assert.h>

extern void ret_from_exception();
extern void printk_task1(void);
extern void __global_pointer$();

pid_t do_spawn(task_info_t *task, void* arg, spawn_mode_t mode)
{   
    pid_t pid = 0;
    int i = 1;
    while(i <= process_id){
        if(pcb[i].status == TASK_NONE)
            pid = i;
        i++;
    }
    if(!pid){
        process_id++;
        pid = process_id;
    }
  	pcb[pid].kernel_sp = allocPage();
  	pcb[pid].user_sp = allocPage();
    pcb[pid].type = task->type;
  	init_pcb_stack(pcb[pid].kernel_sp, pcb[pid].user_sp, task->entry_point, &pcb[pid], arg, NULL);
  	pcb[pid].pid = pid;
  	pcb[pid].status = TASK_READY;
    pcb[pid].preempt_count = 0; //记录屏蔽中断的次数，使能中断时依次打开
    pcb[pid].task_priority = task->priority;
    pcb[pid].priority = task->priority;
    pcb[pid].mode = mode;
    pcb[pid].binsem_id = -1;
    list_add_prior(&pcb[pid].list);
    init_list_head(&pcb[pid].wait_list);
    return pid;
}

pid_t do_exec(const char* file_name, int argc, char* argv[], spawn_mode_t mode){
    pid_t pid = 0;
    int i = 1;
    while(i <= process_id){
        if(pcb[i].status == TASK_NONE)
            pid = i;
        i++;
    }
    if(!pid){
        process_id++;
        pid = process_id;
    }
    // alloc pagetable and stack
    pcb[pid].pgdir = allocPage();
    pcb[pid].kernel_sp = allocPage() + PAGE_SIZE;
    pcb[pid].user_sp = USER_STACK_ADDR;
    alloc_page_helper(pcb[pid].user_sp - 0x1000, pcb[pid].pgdir);
    share_pgtable(pcb[pid].pgdir, pa2kva(PGDIR_PA));

    //load user elf file
    for(i = 0; i < 7; i++)
    {
        if(!kstrcmp(elf_files[i].file_name, file_name)){
            break;
        }
    }
    ptr_t entry_point;
    entry_point = load_elf(elf_files[i].file_content, *(elf_files[i].file_length), pcb[pid].pgdir, alloc_page_helper);

    //copy command line arguments
    uint64_t buff_pointer_kva = alloc_page_helper(USER_EXEC_BUFF, pcb[pid].pgdir);
    uint64_t buff_kva = buff_pointer_kva + argc * 8;
    uint64_t buff_uva = USER_EXEC_BUFF + argc * 8;
    for(i = 0; i < argc; i++)
    {
        int arglen = kstrlen(argv[i]) + 1;
        kmemcpy(buff_kva, argv[i], arglen);
        *(char**)buff_pointer_kva = buff_uva;
        buff_pointer_kva += 8;
        buff_kva += arglen;
        buff_uva += arglen;
    }

    pcb[pid].type = USER_PROCESS;
  	init_pcb_stack(pcb[pid].kernel_sp, pcb[pid].user_sp, entry_point, &pcb[pid], argc, USER_EXEC_BUFF);
  	pcb[pid].pid = pid;
  	pcb[pid].status = TASK_READY;
    pcb[pid].preempt_count = 0; //记录屏蔽中断的次数，使能中断时依次打开
    pcb[pid].task_priority = 1;
    pcb[pid].priority = 1;
    pcb[pid].mode = mode;
    pcb[pid].binsem_id = -1;
    list_add_prior(&pcb[pid].list);
    init_list_head(&pcb[pid].wait_list);
    return pid;
}

void init_pcb_stack(
    ptr_t kernel_stack, ptr_t user_stack, ptr_t entry_point,
    pcb_t *pcb, int argc, void *argv)
{
    regs_context_t *pt_regs =
        (regs_context_t *)(kernel_stack - sizeof(regs_context_t));
    ptr_t new_ksp;
    /* TODO: initialization registers
     * note: sp, gp, ra, sepc, sstatus
     * gp should be __global_pointer$
     * To run the task in user mode,
     * you should set corresponding bits of sstatus(SPP, SPIE, etc.).
     */
    for(size_t i=0; i<32; i++){
      pt_regs->regs[i]=0;
    }
    new_ksp = kernel_stack - sizeof(regs_context_t);
    pcb->kernel_sp = new_ksp;
    pt_regs->regs[2] = user_stack;
    //pt_regs->regs[3] = (reg_t)__global_pointer$;
    pt_regs->regs[4] = (reg_t)pcb;
    pt_regs->regs[10] = (reg_t)argc;
    pt_regs->regs[11] = (reg_t)argv;
    pt_regs->sbadaddr = 0;
    pt_regs->scause = 0;
    pt_regs->sepc = entry_point;	
    pt_regs->sstatus = SR_SPIE | SR_SUM;

    // set sp to simulate return from switch_to
    /* TODO: you should prepare a stack, and push some values to
     * simulate a pcb context.
     */
}

static void init_pcb()
{
     /* initialize all of your pcb and add them into ready_queue
     * TODO:
     */
    init_list_head(&ready_queue);
    init_list_head(&sleep_queue);	
    init_list_head(&timers);
    for(int i = 1; i < NUM_MAX_TASK; i++)
        pcb[i].status = TASK_NONE;

    /* init pid0 pgdir */
    pid0_pcb.pgdir = pa2kva(PGDIR_PA);

    /* init shell */
    do_exec("shell", 0, NULL, ENTER_ZOMBIE_ON_EXIT);

    /* remember to initialize `current_running`
     * TODO:
     */
    current_running = &pid0_pcb;
    current_running_k0 = &pid0_pcb;
    current_running_k1 = &pid0_pcb;
}

static void init_syscall(void)
{
    // initialize system call table.
    for(size_t i = 0; i < NUM_SYSCALLS; i++)
		  syscall[i] = NULL;
    syscall[SYSCALL_SPAWN] = (long (*)())&do_spawn;
    syscall[SYSCALL_EXIT] = (long (*)())&do_exit;
	syscall[SYSCALL_SLEEP] = (long (*)())&do_sleep;
    syscall[SYSCALL_KILL] = (long (*)())&do_kill;
    syscall[SYSCALL_WAITPID] = (long (*)())&do_waitpid;
    syscall[SYSCALL_PS] = (long (*)())&do_ps;
    syscall[SYSCALL_GETPID] = (long (*)())&do_getpid;
    syscall[SYSCALL_EXEC] = (long (*)())&do_exec;
    syscall[SYSCALL_SHOW_EXEC] = (long (*)())&do_show_exec;
	syscall[SYSCALL_BINSEMGET] = (long (*)())&get_binsem;
	syscall[SYSCALL_BINSEMOP] = (long (*)())&op_binsem;
    syscall[SYSCALL_CONDGET] = (long (*)())&get_cond;
	syscall[SYSCALL_CONDOP] = (long (*)())&op_cond;
    syscall[SYSCALL_BARRIERGET] = (long (*)())&get_barrier;
	syscall[SYSCALL_BARRIEROP] = (long (*)())&op_barrier;
    syscall[SYSCALL_MAILBOX_SEND] = (long (*)())&send_mailbox;
	syscall[SYSCALL_MAILBOX_RECV] = (long (*)())&recv_mailbox;
	syscall[SYSCALL_WRITE] = (long (*)())&screen_write;
	syscall[SYSCALL_CURSOR] = (long (*)())&screen_move_cursor;
    syscall[SYSCALL_SCREEN_CLEAR] = (long (*)())&screen_clear;
	syscall[SYSCALL_REFLUSH] = (long (*)())&screen_reflush;
	syscall[SYSCALL_GET_TIMEBASE] = (long (*)())&get_time_base;
	syscall[SYSCALL_GET_TICK] = (long (*)())&get_ticks;
    syscall[SYSCALL_GETCHAR] = (long (*)())&sbi_console_getchar;
    syscall[SYSCALL_PUTCHAR] = (long (*)())&screen_write_ch;
    syscall[SYSCALL_NET_RECV] = (long (*)())&do_net_recv;
    syscall[SYSCALL_NET_SEND] = (long (*)())&do_net_send;
    syscall[SYSCALL_NET_IRQ_MODE]= (long (*)())&do_net_irq_mode;
}

// jump from bootloader.
// The beginning of everything >_< ~~~~~~~~~~~~~~
int main()
{
    int cpu_id;
    cpu_id = get_current_cpu_id();
    if(cpu_id == 0){
        // init free page list
        init_freePage();

        // init Process Control Block (-_-!)
        init_pcb();
        printk("> [INIT] PCB initialization succeeded.\n\r");

        // setup timebase
        // fdt_print(_dtb);
        // get_prop_u32(_dtb, "/cpus/cpu/timebase-frequency", &time_base);
        time_base = sbi_read_fdt(TIMEBASE);
        uint32_t slcr_bade_addr = 0, ethernet_addr = 0;

        // get_prop_u32(_dtb, "/soc/slcr/reg", &slcr_bade_addr);
        slcr_bade_addr = sbi_read_fdt(SLCR_BADE_ADDR);
        printk("[slcr] phy: 0x%x\n\r", slcr_bade_addr);

        // get_prop_u32(_dtb, "/soc/ethernet/reg", &ethernet_addr);
        ethernet_addr = sbi_read_fdt(ETHERNET_ADDR);
        printk("[ethernet] phy: 0x%x\n\r", ethernet_addr);

        uint32_t plic_addr = 0;
        // get_prop_u32(_dtb, "/soc/interrupt-controller/reg", &plic_addr);
        plic_addr = sbi_read_fdt(PLIC_ADDR);
        printk("[plic] plic: 0x%x\n\r", plic_addr);

        uint32_t nr_irqs = sbi_read_fdt(NR_IRQS);
        // get_prop_u32(_dtb, "/soc/interrupt-controller/riscv,ndev", &nr_irqs);
        printk("[plic] nr_irqs: 0x%x\n\r", nr_irqs);

        XPS_SYS_CTRL_BASEADDR =
            (uintptr_t)ioremap((uint64_t)slcr_bade_addr, NORMAL_PAGE_SIZE);
        xemacps_config.BaseAddress =
            (uintptr_t)ioremap((uint64_t)ethernet_addr, NORMAL_PAGE_SIZE);
        uintptr_t _plic_addr =
            (uintptr_t)ioremap((uint64_t)plic_addr, 0x4000*NORMAL_PAGE_SIZE);
        // XPS_SYS_CTRL_BASEADDR = slcr_bade_addr;
        // xemacps_config.BaseAddress = ethernet_addr;
        xemacps_config.DeviceId        = 0;
        xemacps_config.IsCacheCoherent = 0;

        printk(
            "[slcr_bade_addr] phy:%x virt:%lx\n\r", slcr_bade_addr,
            XPS_SYS_CTRL_BASEADDR);
        printk(
            "[ethernet_addr] phy:%x virt:%lx\n\r", ethernet_addr,
            xemacps_config.BaseAddress);
        printk("[plic_addr] phy:%x virt:%lx\n\r", plic_addr, _plic_addr);
        plic_init(_plic_addr, nr_irqs);
    
        long status = EmacPsInit(&EmacPsInstance);
        if (status != XST_SUCCESS) {
            printk("Error: initialize ethernet driver failed!\n\r");
            assert(0);
        }
	
        // init futex mechanism
        // init_system_futex();
        init_binsems();
        init_conds();
        init_barriers();

        // init interrupt (^_^)
        init_exception();
        printk("> [INIT] Interrupt processing initialization succeeded.\n\r");

        // init system call table (0_0)
        init_syscall();
        printk("> [INIT] System call initialized successfully.\n\r");

        // enable_interrupt();
        net_poll_mode = 1;
        // xemacps_example_main();

        // init screen (QAQ)
        init_screen();
        printk("> [INIT] SCREEN initialization succeeded.\n\r");

        // TODO:
        // Setup timer interrupt and enable all interrupt

        wakeup_other_hart();
    }
    sbi_set_timer(get_ticks() + time_base/100);
    setup_exception();
    while (1) {
        // (QAQQQQQQQQQQQ)
        // If you do non-preemptive scheduling, you need to use it
        // to surrender control do_scheduler();
        enable_interrupt();
        __asm__ __volatile__("wfi\n\r":::);
        //do_scheduler();
    };
    return 0;
}
